# Server-Side Rendering (SSR)

This chapter introduces how to implement SSR functionality using Rsbuild.

Please note that Rsbuild itself does not provide out-of-the-box SSR functionality, but instead provides low-level APIs and configurations to allow framework developers to implement SSR. If you require out-of-the-box SSR support, you may consider using full-stack frameworks based on Rsbuild, such as [Modern.js](https://github.com/web-infra-dev/modern.js).

## What is SSR

SSR stands for "Server-Side Rendering". It means that the HTML of the web page is generated by the server and sent to the client, rather than sending only an empty HTML shell and relying on JavaScript to generate the page content.

In traditional client-side rendering, the server sends an empty HTML shell and some JavaScript scripts to the client, and then fetching data from the server's API and fills the page with dynamic content. This leads to slow initial page loading times and is not conducive to user experience and SEO.

With SSR, the server generates HTML that already contains dynamic content and sends it to the client. This makes the initial page loading faster and more SEO-friendly, as search engines can crawl the rendered page.

## File Structure

A typical SSR application will have the following files:

```
- index.html
- server.js          # main application server
- src/
  - App.js           # exports app code
  - index.client.js  # client entry, mounts the app to a DOM element
  - index.server.js  # server entry, renders the app using the framework's SSR API
```

The `index.html` will need to include a placeholder where the server-rendered markup should be injected:

```html
<div id="root"><!--app-content--></div>
```

## Create SSR configuration

In the SSR scenario, two types of outputs (web and node targets), need to be generated at the same time, for client-side rendering (CSR) and server-side rendering (SSR) respectively.

At this time, you can use Rsbuild's [multi-environment builds](guide/advanced/environments) capability, define the following configuration:

```ts title=rsbuild.config.ts
export default {
  environments: {
    // Configure the web environment for browsers
    web: {
      source: {
        entry: {
          index: './src/index.client.js',
        },
      },
      output: {
        // Use 'web' target for the browser outputs
        target: 'web',
      },
      html: {
        // Custom HTML template
        template: './index.html',
      },
    },
    // Configure the node environment for SSR
    node: {
      source: {
        entry: {
          index: './src/index.server.js',
        },
      },
      output: {
        // Use 'node' target for the Node.js outputs
        target: 'node',
      },
    },
  },
};
```

## Custom server

Rsbuild does not have built-in SSR rendering capabilities, but you can implement SSR rendering through Rsbuild's [custom server](/guide/basic/server#custom-server) and [environment API](/guide/advanced/environments#environment-api):

```ts title=server.mjs
import express from 'express';
import { createRsbuild, loadConfig } from '@rsbuild/core';

// Implement SSR rendering function
const serverRender = (serverAPI) => async (_req, res) => {
  // Load Node bundle for SSR
  const indexModule = await serverAPI.environments.node.loadBundle('index');

  const markup = indexModule.render();

  const template = await serverAPI.environments.web.getTransformedHtml('index');

  // Insert SSR rendering content into HTML template
  const html = template.replace('<!--app-content-->', markup);

  res.writeHead(200, {
    'Content-Type': 'text/html',
  });
  res.end(html);
};

// Custom server
async function startDevServer() {
  const { content } = await loadConfig({});

  const rsbuild = await createRsbuild({
    rsbuildConfig: content,
  });

  const app = express();

  const rsbuildServer = await rsbuild.createDevServer();

  const serverRenderMiddleware = serverRender(rsbuildServer);

  // SSR rendering when accessing /index.html
  app.get('/', async (req, res, next) => {
    try {
      await serverRenderMiddleware(req, res, next);
    } catch (err) {
      logger.error('SSR render error, downgrade to CSR...\n', err);
      next();
    }
  });

  app.use(rsbuildServer.middlewares);

  const httpServer = app.listen(rsbuildServer.port, async () => {
    await rsbuildServer.afterListen();
  });

  rsbuildServer.connectWebSocket({ server: httpServer });
}

startDevServer(process.cwd());
```

## Modify startup script

After using a custom Server, you need to change the startup command from `rsbuild dev` to `node ./server.mjs`.

If you need to preview the online effect of SSR rendering, you also need to modify the preview command. SSR Prod Server example reference: [Example](https://github.com/rspack-contrib/rspack-examples/blob/main/rsbuild/ssr-express/prod-server.mjs).

```json title=package.json
{
  "scripts": {
    "build": "rsbuild build",
    "dev": "node ./server.mjs",
    "preview": "node ./prod-server.mjs"
  }
}
```

Now, you can run the `npm run dev` command to start the dev server with SSR rendering function, and visit `http://localhost:3000/` to see that the SSR content has been rendered to the HTML page.

## Get Manifest

By default, scripts and links associated with the current page are automatically inserted into the HTML template. At this time, the compiled HTML template content can be obtained through [getTransformedHtml](/guide/advanced/environments#environment-api).

When you need to dynamically generate HTML on the server side, you'll need to inject the URLs of JavaScript and CSS assets into the HTML. By configuring [output.manifest](/config/output/manifest), you can easily obtain the manifest information of these assets. Here's an example:

```ts title=rsbuild.config.ts
export default {
  output: {
    manifest: true,
  },
};
```

```ts title=server.ts
async function renderHtmlPage(): Promise<string> {
  const manifest = await fs.promises.readFile('./dist/manifest.json', 'utf-8');
  const { entries } = JSON.parse(manifest);

  const { js, css } = entries['index'].initial;

  const scriptTags = js
    .map((url) => `<script src="${url}" defer></script>`)
    .join('\n');
  const styleTags = css
    .map((file) => `<link rel="stylesheet" href="${file}">`)
    .join('\n');

  return `
    <!DOCTYPE html>
    <html>
      <head>
        ${scriptTags}
        ${styleTags}
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>`;
}
```

## Examples

- [SSR + Express Example](https://github.com/rspack-contrib/rspack-examples/blob/main/rsbuild/ssr-express)
- [SSR + Express + Manifest Example](https://github.com/rspack-contrib/rspack-examples/blob/main/rsbuild/ssr-express-with-manifest)
